//
//  Copyright (C) 2009 Vadim Ushakov
// 
//  This program is free software; you can redistribute it and/or modify it
//  under the terms of the GNU General Public License as published by the Free
//  Software Foundation; either version 2 of the License, or (at your option)
//  any later version.
// 
//  This program is distributed in the hope that it will be useful, but
//  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
//  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
//  for more details.
//  
//  You should have received a copy of the GNU General Public License along
//  with this program; if not, write to the Free Software Foundation, Inc.,
//  59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//  perfmon.c
//

/*
<help>

 -- perfmon --

Performance monitor.

Usage:
  perfmon

(Only available in graphics mode)

The perfmon program is an interactive utility to show values of kernel
performance counters.

</help>
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/api.h>
#include <sys/vsh.h>

#define SHOW_MAX_ITEMS 64

static int processId = 0;
static int privilege = 0;
static listItemParameters *counterListParams = NULL;
static objectKey window = NULL;
static objectKey counterList = NULL;
static int stop = 0;
static int numListItems = 0;


typedef struct _perfCounter
{
	char* name;
	int   index;
	int   prevValue;
	int   currValue;
	int   deltaValue;
} perfCounter;

static perfCounter perfCounters[] = 
{
	{"multitasker.dispatch_calls"       , -1, 0, 0, 0},
	{"multitasker.dispatch_yield_calls" , -1, 0, 0, 0},
	{"multitasker.dispatch_async_calls" , -1, 0, 0, 0},
	{"multitasker.dispatch_reschedule_calls", -1, 0, 0, 0},
	{"multitasker.context_switches"     , -1, 0, 0, 0},
	{"multitasker.wait_calls"           , -1, 0, 0, 0},
	{"multitasker.block_calls"          , -1, 0, 0, 0},
	{"multitasker.processes_created"    , -1, 0, 0, 0},
	{"multitasker.fpu_exceptions"       , -1, 0, 0, 0},
	{"multitasker.fpu_context_saved"    , -1, 0, 0, 0},
	{"multitasker.fpu_context_restored" , -1, 0, 0, 0},
	{"kernel.api_calls"                 , -1, 0, 0, 0},
	{"kernel.locks_acquired"            , -1, 0, 0, 0},
	{"kernel.ticks"                     , -1, 0, 0, 0},
	{"kernel.cycles"                    , -1, 0, 0, 0},
	{"kernel.microseconds"               , -1, 0, 0, 0},
	{NULL, -1, 0, 0, 0}
};


__attribute__((format(printf, 1, 2)))
static void error(const char *format, ...)
{
	// Generic error message code for either text or graphics modes
  
	va_list list;
	char output[MAXSTRINGLENGTH];
  
	va_start(list, format);
	vsnprintf(output, MAXSTRINGLENGTH, format, list);
	va_end(list);

	windowNewErrorDialog(window, "Error", output);
}

static void updateCounter(perfCounter* counter)
{
	if (!counter)
		return;

	if (!counter->name)
		return;

	if (counter->index < 0)
		counter->index = performanceCounterGetIndex(counter->name);

	if (counter->index < 0)
	{
		counter->prevValue = -1;
		counter->currValue = -1;
		counter->deltaValue = 0;
	}
	else
	{
		counter->prevValue = counter->currValue;
		counter->currValue = performanceCounterGetI(counter->index);
		counter->deltaValue = counter->currValue - counter->prevValue;
	}
}

static int getUpdate(void)
{
	int status = 0;
	char *bufferPointer = NULL;
	int count;

	for (count = 0; perfCounters[count].name != NULL; count ++)
	{
		updateCounter(&perfCounters[count]);
	}

	for (count = 0; perfCounters[count].name != NULL; count ++)
	{
		memset(counterListParams[count].text, ' ', WINDOW_MAX_LABEL_LENGTH);
		bufferPointer = counterListParams[count].text;
		perfCounter* counter = &perfCounters[count];

		sprintf(bufferPointer, "%s", counter->name);
		bufferPointer[strlen(bufferPointer)] = ' ';

		sprintf((bufferPointer + 41), "%d", counter->currValue);
		bufferPointer[strlen(bufferPointer)] = ' ';

		sprintf((bufferPointer + 52), "%d", counter->deltaValue);
		bufferPointer[strlen(bufferPointer)] = ' ';

	}

	numListItems = count;

	return (status = 0);
}


static void eventHandler(objectKey key, windowEvent *event)
{
	// Check for the window being closed by a GUI event.
	if ((key == window) && (event->type == EVENT_WINDOW_CLOSE))
	{
		stop = 1;
		windowGuiStop();
		windowDestroy(window);
	}

}


static void constructWindow(void)
{
	// If we are in graphics mode, make a window rather than operating on the
	// command line.

	componentParameters params;

	// Create a new window
	window = windowNew(processId, "Performance Monitor");
	if (window == NULL)
		return;

	bzero(&params, sizeof(componentParameters));
	params.gridWidth = 1;
	params.gridHeight = 1;
	params.padLeft = 5;
	params.padTop = 5;
	params.orientationX = orient_left;
	params.orientationY = orient_top;

	params.gridX = 0;
	params.gridY += 2;
	params.gridWidth = 2;
	params.padBottom = 0;
	fontGetDefault(&(params.font));
	// Create the label of column headers for the list below
	windowNewTextLabel(window, "Counter                                  Value      Delta    ", &params);

	params.padBottom = 5;
	params.padRight  = 5;

	params.gridY += 1;
	counterList = windowNewList(window, windowlist_textonly, 20, 1, 0, counterListParams, numListItems, &params);

	// Register an event handler to catch window close events
	windowRegisterEventHandler(window, &eventHandler);

	windowSetVisible(window, 1);

	return;
}


int main(int argc, char *argv[])
{
	int status = 0;
	int guiThreadPid = 0;

	// Only work in graphics mode
	if (!graphicsAreEnabled())
	{
		printf("\nThe \"%s\" command only works in graphics mode\n", (argc? argv[0] : ""));
		return (status = ERR_NOTINITIALIZED);
	}

	processId = multitaskerGetCurrentProcessId();
	privilege = multitaskerGetProcessPrivilege(processId);

	counterListParams = malloc(SHOW_MAX_ITEMS * sizeof(listItemParameters));

	if (counterListParams == NULL)
	{
		if (counterListParams)
			free(counterListParams);
		error("Error getting memory");
		return (status = ERR_MEMORY);
	}

	status = getUpdate();
	if (status < 0)
	{
		free(counterListParams);
		errno = status;
		if (argc)
			perror(argv[0]);
		return (status);
	}

	// Make our window
	constructWindow();

	// Run the GUI
	guiThreadPid = windowGuiThread();

	while (!stop && multitaskerProcessIsAlive(guiThreadPid))
	{
		if (getUpdate() < 0)
			break;
		windowComponentSetData(counterList, counterListParams, numListItems);
		multitaskerWait(20);
	}

	free(counterListParams);
	return (status = 0);
}
